package com.clp.protocol.iec104.connect;

import com.clp.protocol.iec104.apdu.Apdu;
import com.clp.protocol.iec104.apdu.apci.ICtrlArea;
import com.clp.protocol.iec104.apdu.apci.SCtrlArea;
import com.clp.protocol.iec104.connect.config.ConnConfig;
import com.clp.protocol.iec104.definition.ConstVal;
import com.clp.protocol.iec104.exception.IncorrectRecvSeqException;
import com.clp.protocol.iec104.exception.IncorrectRtuAddrException;
import com.clp.protocol.iec104.exception.IncorrectSendSeqException;
import com.clp.protocol.iec104.exception.IncorrectSeqException;
import io.netty.channel.Channel;
import lombok.extern.slf4j.Slf4j;

import java.net.InetSocketAddress;
import java.util.Objects;
import java.util.concurrent.ScheduledExecutorService;

/**
 * 连接持有者
 */
@Slf4j
public abstract class ConnInfoImpl implements ConnInfo {
    private volatile Channel channel;
    private final String remoteHost;
    private final int remotePort;
    private final int rtuAddr; // 公共地址验证

    private final int maxAckNum; // 最大累计确认数

    private volatile int sendSeq = 0; // 发送序号，最大值32768-1
    private volatile int recvSeq = 0; // 接收序号，最大值32768-1
    private volatile int ackSeq = 0; // 发送出去的已被接收到的序号
    private volatile int currRecvCount = 0; // 当前累计收到I帧数

    private final ScheduledExecutorService scheduledExecutor;

    protected ConnInfoImpl(ConnConfig cfg, ScheduledExecutorService scheduledExecutor) {
        // 设置通道、ip、端口号
        this.remoteHost = cfg.getRemoteHost();
        this.remotePort = cfg.getRemotePort();
        // 公共地址
        this.rtuAddr = cfg.getRtuAddr();
        // 最大确认帧数
        this.maxAckNum = cfg.getMaxAckNum();
        // 定时任务执行器
        this.scheduledExecutor = scheduledExecutor;
    }

    /**
     * 重置
     *
     * @param channel 通道
     */
    public void resetChannel(Channel channel) {
        InetSocketAddress ipSocket = (InetSocketAddress) channel.remoteAddress();
        if ((this.remoteHost != null && !this.remoteHost.equals(ipSocket.getHostString()))
                || (this.remotePort != ipSocket.getPort())) {
            throw new RuntimeException("不能重置为不同ip:port的连接！");
        }
        this.channel = channel;

        this.sendSeq = 0;
        this.recvSeq = 0;
        this.ackSeq = 0;
        this.currRecvCount = 0;
    }

    @Override
    public boolean isConnected() {
        return channel != null && channel.isOpen() && channel.isActive();
    }

    @Override
    public ScheduledExecutorService scheduledExecutor() {
        return scheduledExecutor;
    }

    /**
     * 经校验的 channel
     *
     * @return 通道
     */
    public Channel channel() {
        if (!isConnected()) throw new RuntimeException("无效的 Channel！");
        return channel;
    }

    @Override
    public String remoteHost() {
        return remoteHost;
    }

    @Override
    public int remotePort() {
        return remotePort;
    }

    @Override
    public int rtuAddr() {
        return rtuAddr;
    }

    @Override
    public int sendSeq() {
        return sendSeq;
    }

    @Override
    public int recvSeq() {
        return recvSeq;
    }

    @Override
    public int currRecvCount() {
        return currRecvCount;
    }

    /**
     * 增加发送序号
     */
    protected synchronized void addSendSeqCircularly() {
        sendSeq = (sendSeq + 1) % ConstVal.MAX_SEQ;
    }

    /**
     * 增加接收序号
     */
    protected synchronized void addRecvSeqCircularly() {
        recvSeq = (recvSeq + 1) % ConstVal.MAX_SEQ;
    }

    /**
     * 循环增加累计接收I帧数。以累计最大接收数进行循环
     */
    protected synchronized void addCurrRecvCountCircularly() {
        this.currRecvCount = (currRecvCount + 1) % maxAckNum;
    }

    /**
     * 重置累计确认数为0
     */
    protected synchronized void resetCurrRecvCount() {
        this.currRecvCount = 0;
    }

    @Override
    public void checkRecvApduRtuAddr(Apdu apdu) throws IncorrectRtuAddrException {
        if (!apdu.isIType()) return;
        int recvedRtuAddr = apdu.castToIType().getIAsdu().getRtuAddr();
        if (recvedRtuAddr != rtuAddr) {
            throw new IncorrectRtuAddrException(rtuAddr, recvedRtuAddr);
        }
    }

    @Override
    public void checkRecvApduSeq(Apdu apdu) throws IncorrectSeqException {
        if (apdu.isSType()) {
            SCtrlArea sControlArea = apdu.castToSType().getSApci().getCtrlArea();
            int remoteRecvSeq = sControlArea.getRecvSeq();
            if (remoteRecvSeq > sendSeq) {
                throw new IncorrectRecvSeqException(remoteRecvSeq);
            }
        } else if (apdu.isIType()) {
            ICtrlArea iControlArea = apdu.castToIType().getIApci().getCtrlArea();
            int remoteSendSeq = iControlArea.getSendSeq();
            int remoteRecvSeq = iControlArea.getRecvSeq();
            if (remoteSendSeq < recvSeq) {
                throw new IncorrectSendSeqException(remoteSendSeq);
            }
            if (remoteRecvSeq > sendSeq) {
                throw new IncorrectRecvSeqException(remoteRecvSeq);
            }
        }
    }

    protected abstract boolean needResetCurrRecvCountByRecving(Apdu apdu);

    protected boolean needSendSApduOfAck() {
        return currRecvCount == 0;
    }

    @Override
    public boolean equals(Object o) {
        if (this == o) return true;
        if (o == null || getClass() != o.getClass()) return false;
        ConnInfoImpl that = (ConnInfoImpl) o;
        return remotePort == that.remotePort && Objects.equals(remoteHost, that.remoteHost);
    }

    @Override
    public int hashCode() {
        return Objects.hash(remoteHost, remotePort);
    }
}
